package jenkins

import (
	"context"
	"encoding/json"
	"fmt"
	"gorm.io/gorm"
	"istio.io/api/networking/v1alpha3"
	istioV3 "istio.io/client-go/pkg/applyconfiguration/networking/v1alpha3"
	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/yaml"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/tools/clientcmd"
	"project-info/src/handle/response"
	"project-info/src/model"

	"project-info/src/lib")

const DefaultGateway = "mesh"

var ServiceVersion = schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}
var GatewayVersion = schema.GroupVersionResource{Group: "networking.istio.io", Version: "v1alpha3", Resource: "gateways"}
var VirtualServiceVersion = schema.GroupVersionResource{Group: "networking.istio.io", Version: "v1alpha3", Resource: "virtualservices"}
var DestinationRuleVersion = schema.GroupVersionResource{Group: "networking.istio.io", Version: "v1alpha3", Resource: "destinationrules"}

type FlowControlDto struct {
	Id           uint                     `json:"id"`
	ItemId       uint                     `json:"item_id"`
	DefaultFlow  []map[string]interface{} `json:"default_flow"`
	AdvancedFlow []map[string]interface{} `json:"advanced_flow"`
}

type ServiceDto struct {
	Name string `json:"name"`
	Port int32  `json:"port"`
}

type DeployConfigDto struct {
	Service ServiceDto `json:"service"`
}

// UpdateFlowControl 创建或更新流量控制
func UpdateFlowControl(c *lib.GinContext) error {
	var flowControl *model.FlowControl
	if err := c.Bind(&flowControl); err != nil {
		c.Logger().Error(err.Error())
		return response.JsonFail(c, "参数解析错误")
	}

	var versions []*model.ItemVersion
	model.DB().Where("item_id = ?", flowControl.ItemId).Find(&versions)

	var item *model.Item
	if err := model.DB().Where("id = ?", flowControl.ItemId).First(&item).Error; err != nil && err != gorm.ErrRecordNotFound {
		c.Logger().Error(err.Error())
		return response.JsonFail(c, "查询失败")
	}

	err := processUpdateFlowControl(c, flowControl, item, versions...)
	if err != nil {
		c.Logger().Error(err.Error())
		return response.JsonFail(c, "处理流量控制出错")
	}
	return response.JsonOk(c, flowControl, "success")
}

// processUpdateFlowControl 处理流量控制
func processUpdateFlowControl(c *lib.GinContext, flowControl *model.FlowControl, item *model.Item, itemVersions ...*model.ItemVersion) error {
	//1.保存流量控制
	var dbFlowControl model.FlowControl
	if err := model.DB().Where("item_id = ?", flowControl.ItemId).First(&dbFlowControl).Error; err != nil && err == gorm.ErrRecordNotFound {
		model.DB().Create(flowControl)
	} else {
		flowControl.Id = dbFlowControl.Id
		model.DB().Save(flowControl)
	}

	//2.拼接gateway，VirtualService，DestinationRule，Service文件
	dynamicClient, err := buildK8sClient(c, item)
	if err != nil {
		return err
	}

	flowControlDto, err := convertFlowControlDto(flowControl)
	if err != nil {
		return err
	}

	versionDestination, err := convertVersionDestination(item, itemVersions)
	if err != nil {
		return err
	}

	err = applyService(dynamicClient, item, versionDestination)
	if err != nil {
		return err
	}

	err = applyGateway(dynamicClient, item)
	if err != nil {
		return err
	}

	err = applyVirtualService(dynamicClient, item, versionDestination, flowControlDto)
	if err != nil {
		return err
	}

	err = applyDestinationRule(dynamicClient, item, versionDestination, flowControlDto)
	if err != nil {
		return err
	}
	return nil
}

// convertFlowControlDto 处理删除流量控制逻辑
func processDeleteFlowControl(c *lib.GinContext, flowControl model.FlowControl) error {
	//1.删除流量控制
	var item *model.Item
	err := model.DB().Where("id = ?", flowControl.ItemId).Find(&item).Error
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}
	err = model.DB().Where("item_id = ?", flowControl.ItemId).Delete(&model.FlowControl{}).Error
	if err != nil {
		c.Logger().Error(err.Error())
	}
	//2.删除gateway，VirtualService，DestinationRule，Service
	dynamicClient, err := buildK8sClient(c, item)
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}

	err = commonDelete(dynamicClient, ServiceVersion, item.Namespace, item.AppName)
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}
	err = commonDelete(dynamicClient, GatewayVersion, item.Namespace, item.AppName)
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}
	err = commonDelete(dynamicClient, VirtualServiceVersion, item.Namespace, item.AppName)
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}
	err = commonDelete(dynamicClient, DestinationRuleVersion, item.Namespace, item.AppName)
	if err != nil {
		c.Logger().Error(err.Error())
		return err
	}
	return nil
}

// GetFlowControl 获取流量控制
func GetFlowControl(c *lib.GinContext) error {
	var flowControl *model.FlowControl
	if err := model.DB().Where("item_id = ?", c.QueryParam("item_id")).First(&flowControl).Error; err != nil && err != gorm.ErrRecordNotFound {
		c.Logger().Error(err.Error())
		return response.JsonFail(c, "查询失败")
	}

	return response.JsonOk(c, flowControl, "success")
}

// applyDestinationRule 创建或更新DestinationRule
func applyDestinationRule(client *dynamic.DynamicClient, item *model.Item, verionDestination map[string]*v1alpha3.Destination, dto *FlowControlDto) error {
	// convert versions to v1alpha3.Subset
	subsets := make([]*v1alpha3.Subset, 0)
	versionMap := make(map[string]bool)

	// 只创建流量管理中需要的版本
	for _, flow := range dto.DefaultFlow {
		version := flow["version"].(string)
		if _, ok := versionMap[version]; !ok {
			subsets = append(subsets, &v1alpha3.Subset{
				Name: version,
				Labels: map[string]string{
					"version": version,
				},
			})
			versionMap[version] = true
		}
	}

	for _, flow := range dto.AdvancedFlow {
		version := flow["version"].(string)
		if _, ok := versionMap[version]; !ok {
			subsets = append(subsets, &v1alpha3.Subset{
				Name: version,
				Labels: map[string]string{
					"version": version,
				},
			})
			versionMap[version] = true
		}
	}

	destinationRule := istioV3.DestinationRule(item.AppName, item.Namespace).WithSpec(v1alpha3.DestinationRule{
		Host:    item.AppName,
		Subsets: subsets,
	})
	return commonApply(client, DestinationRuleVersion, item, destinationRule)
}

// applyVirtualService 创建或更新VirtualService
func applyVirtualService(client *dynamic.DynamicClient, item *model.Item, versionDestination map[string]*v1alpha3.Destination, dto *FlowControlDto) error {
	// convert flowControlDto.DefaultFlow and flowControlDto.AdvancedFlow to v1alpha3.HTTPRoute
	httpRoutes := make([]*v1alpha3.HTTPRoute, 0)

	// advanced flow的匹配路由处理
	for _, flow := range dto.AdvancedFlow {
		httpRoutes = append(httpRoutes, &v1alpha3.HTTPRoute{
			Match: []*v1alpha3.HTTPMatchRequest{
				getHttpMatchRequest(flow),
			},
			Route: []*v1alpha3.HTTPRouteDestination{
				{
					Destination: versionDestination[flow["version"].(string)],
				},
			},
		})
	}

	// default flow的默认路由处理
	defaultHttpRouteDestinations := make([]*v1alpha3.HTTPRouteDestination, 0)
	for _, flow := range dto.DefaultFlow {
		defaultHttpRouteDestinations = append(defaultHttpRouteDestinations, &v1alpha3.HTTPRouteDestination{
			Destination: versionDestination[flow["version"].(string)],
			Weight:      int32(flow["percent"].(float64)),
		})
	}
	defaultRoute := &v1alpha3.HTTPRoute{
		Route: defaultHttpRouteDestinations,
	}

	httpRoutes = append(httpRoutes, defaultRoute)
	virtualService := istioV3.VirtualService(item.AppName, item.Namespace).WithSpec(v1alpha3.VirtualService{
		Hosts: []string{
			item.Domain,
			fmt.Sprintf("%s.%s.svc.cluster.local", item.AppName, item.Namespace),
		},
		Gateways: []string{
			item.AppName,
			DefaultGateway,
		},
		Http: httpRoutes,
	})
	return commonApply(client, VirtualServiceVersion, item, virtualService)
}

// applyGateway apply gateway对象
func applyGateway(dynamicClient *dynamic.DynamicClient, item *model.Item) error {
	gateway := istioV3.Gateway(item.AppName, item.Namespace).WithSpec(v1alpha3.Gateway{
		Servers: []*v1alpha3.Server{
			{
				Port: &v1alpha3.Port{
					Number:   80,
					Name:     "http",
					Protocol: "HTTP",
				},
				Hosts: []string{item.Domain},
			},
		},
	})

	return commonApply(dynamicClient, GatewayVersion, item, gateway)
}

// applyService apply service对象
func applyService(dynamicClient *dynamic.DynamicClient, item *model.Item, versionDestination map[string]*v1alpha3.Destination) error {
	servicePorts := make([]v1.ServicePort, 0)
	ServicePortMap := make(map[uint32]bool)
	for _, destination := range versionDestination {
		if _, ok := ServicePortMap[destination.Port.Number]; !ok {
			servicePorts = append(servicePorts, v1.ServicePort{
				Name:       fmt.Sprintf("http-%d", destination.Port.Number),
				Port:       int32(destination.Port.Number),
				TargetPort: intstr.FromInt32(int32(destination.Port.Number)),
				Protocol:   v1.ProtocolTCP,
			})
			ServicePortMap[destination.Port.Number] = true
		}
	}

	service := &v1.Service{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "v1",
			Kind:       "Service",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name:      item.AppName,
			Namespace: item.Namespace,
		},
		Spec: v1.ServiceSpec{
			Ports: servicePorts,
			Selector: map[string]string{
				"app": item.AppName,
			},
			Type: v1.ServiceTypeClusterIP,
		},
	}

	return commonApply(dynamicClient, ServiceVersion, item, service)
}

// 通用的apply方法，根据online是否存在来判断是create还是update
// 拼接出要apply的资源对象，再统一转换成unstructured对象，再调用dynamicClient的create或update方法，转换时把online的metadata也带上
func commonApply(dynamicClient *dynamic.DynamicClient, resource schema.GroupVersionResource, item *model.Item, obj interface{}) error {
	online, err := getOnlineResource(dynamicClient, resource, item.Namespace, item.AppName)
	if err != nil {
		if statusError, ok := err.(*errors.StatusError); ok {
			if statusError.ErrStatus.Code != 404 {
				return err
			}
		} else {
			return err
		}
	}

	u, err := toUnstructured(obj, online)
	if err != nil {
		return err
	}

	if online == nil {
		_, err = dynamicClient.Resource(resource).Namespace(item.Namespace).Create(context.TODO(), u, metav1.CreateOptions{})
	} else {
		_, err = dynamicClient.Resource(resource).Namespace(item.Namespace).Update(context.TODO(), u, metav1.UpdateOptions{})
	}

	return err
}

// commonDelete 通用的delete方法
func commonDelete(dynamicClient *dynamic.DynamicClient, resource schema.GroupVersionResource, namespace string, name string) error {
	return dynamicClient.Resource(resource).Namespace(namespace).Delete(context.TODO(), name, metav1.DeleteOptions{})
}

// getOnlineResource 获取online的资源对象
func getOnlineResource(dynamicClient *dynamic.DynamicClient, resource schema.GroupVersionResource, namespace string, name string) (*unstructured.Unstructured, error) {
	return dynamicClient.Resource(resource).Namespace(namespace).Get(context.TODO(), name, metav1.GetOptions{})
}

// toUnstructured 将obj转换成unstructured对象，如果onlineObj不为空，把onlineObj的metadata也带上
func toUnstructured(obj interface{}, onlineObj *unstructured.Unstructured) (*unstructured.Unstructured, error) {
	unstructuredObj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
	if err != nil {
		return nil, err
	}

	r := &unstructured.Unstructured{
		Object: unstructuredObj,
	}
	if onlineObj != nil {
		r.SetResourceVersion(onlineObj.GetResourceVersion())
		r.SetUID(onlineObj.GetUID())
	}
	return r, nil
}

// convertVersionDestination 将itemVersion中的DeployConfig转换成Destination,里面有各个版本对应的Service.Port
func convertVersionDestination(item *model.Item, itemVersions []*model.ItemVersion) (map[string]*v1alpha3.Destination, error) {
	versionDestinations := make(map[string]*v1alpha3.Destination)
	for _, itemVersion := range itemVersions {
		var deployConfigDto DeployConfigDto
		err := yaml.Unmarshal([]byte(itemVersion.DeployConfig), &deployConfigDto)
		if err != nil {
			return nil, err
		}
		versionDestinations[itemVersion.Version] = &v1alpha3.Destination{
			Host:   item.AppName,
			Subset: itemVersion.Version,
			Port: &v1alpha3.PortSelector{
				Number: uint32(deployConfigDto.Service.Port),
			},
		}
	}
	return versionDestinations, nil
}

// getStringMatch 根据flow的matchType类型返回对应的HTTPMatchRequest
func getHttpMatchRequest(flow map[string]interface{}) *v1alpha3.HTTPMatchRequest {
	var httpMatchRequest *v1alpha3.HTTPMatchRequest
	switch flow["matchType"] {
	case "uri":
		httpMatchRequest = &v1alpha3.HTTPMatchRequest{
			Uri: getStringMatch(flow),
		}
	case "scheme":
		httpMatchRequest = &v1alpha3.HTTPMatchRequest{
			Scheme: getStringMatch(flow),
		}
	case "method":
		httpMatchRequest = &v1alpha3.HTTPMatchRequest{
			Method: getStringMatch(flow),
		}
	case "authority":
		httpMatchRequest = &v1alpha3.HTTPMatchRequest{
			Authority: getStringMatch(flow),
		}
	case "headers":
		httpMatchRequest = &v1alpha3.HTTPMatchRequest{
			Headers: map[string]*v1alpha3.StringMatch{
				flow["key"].(string): getStringMatch(flow),
			},
		}
	}
	return httpMatchRequest
}

// getStringMatch 根据flow中的exp和value生成StringMatch对象
func getStringMatch(flow map[string]interface{}) *v1alpha3.StringMatch {
	var stringMatch *v1alpha3.StringMatch
	switch flow["exp"] {
	case "exact":
		stringMatch = &v1alpha3.StringMatch{
			MatchType: &v1alpha3.StringMatch_Exact{
				Exact: flow["value"].(string),
			},
		}
	case "prefix":
		stringMatch = &v1alpha3.StringMatch{
			MatchType: &v1alpha3.StringMatch_Prefix{
				Prefix: flow["value"].(string),
			},
		}
	case "regex":
		stringMatch = &v1alpha3.StringMatch{
			MatchType: &v1alpha3.StringMatch_Regex{
				Regex: flow["value"].(string),
			},
		}
	}
	return stringMatch
}

// convertFlowControlDto 转换FlowControl对象成FlowControlDto
func convertFlowControlDto(flowControl *model.FlowControl) (*FlowControlDto, error) {
	var defaultFlow []map[string]interface{}
	err := json.Unmarshal(flowControl.DefaultFlow, &defaultFlow)
	if err != nil {
		return nil, err
	}

	var advancedFlow []map[string]interface{}
	err = json.Unmarshal(flowControl.AdvancedFlow, &advancedFlow)
	if err != nil {
		return nil, err
	}

	flowControlDto := &FlowControlDto{
		Id:           flowControl.Id,
		ItemId:       flowControl.ItemId,
		DefaultFlow:  defaultFlow,
		AdvancedFlow: advancedFlow,
	}
	return flowControlDto, err
}

// buildK8sClient 创建k8s client
func buildK8sClient(c *lib.GinContext, item *model.Item) (*dynamic.DynamicClient, error) {
	k8sCluster, err := resourceService.GetK8sConfig(c, item)
	if err != nil {
		return nil, err
	}

	config, err := clientcmd.Load([]byte(k8sCluster.Config))
	if err != nil {
		return nil, err
	}
	restConfig, err := clientcmd.NewDefaultClientConfig(*config, &clientcmd.ConfigOverrides{}).ClientConfig()

	dynamicClient, err := dynamic.NewForConfig(restConfig)
	if err != nil {
		return nil, err
	}
	return dynamicClient, nil
}
